iOS AutoLayout之 - NSLayoutConstraint

AutoLayout是在iOS6之后推出的一种基于约束的,描述性的布局系统。使用约束条件来定义view的位置和尺寸。解决了不同分辨率和屏幕尺寸下view的适配问题,也简化了旋转时view位置的定义。
之前一直使用frame或第三方库Masonry布局,对约束如何实现的并不清楚,所以抽时间进行了简单学习,并记录下如何使用纯代码NSLayoutConstraint实现autoLayout。

显式创建约束方法constraintWithItem

+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(nullable id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)constant;
  • view1:要添加约束的视图
  • attr1:约束枚举NSLayoutAttribute
  • relation:与约束值的关系,大于 等于或小于
  • view2:被参照对象
  • attr2:被参照对象所被参照的枚举值NSLayoutAttribute
  • multiplier:乘数,确定间距倍数等关系(默认1.0)
  • constant:差值常量

    注:view1与view2的位置和大小约束关系满足表达式:
    view1.attr1 = view2.attr2 * multiplier + constant

简单介绍下NSLayoutAttribute的枚举值:

NSLayoutAttributeLeft: // 视图的x坐标,相当于CGRectGetMinX(view.frame)
NSLayoutAttributeRight: // CGRectGetMaxX(view.frame);
NSLayoutAttributeTop: // CGRectGetMinY(view.frame);
NSLayoutAttributeBottom: // CGRectGetMinY(view.frame);
NSLayoutAttributeWidth: // 视图宽度
NSLayoutAttributeHeight: // 视图高度
NSLayoutAttributeCenterX: // 视图中点的X值
NSLayoutAttributeCenterY: // 视图中点的Y值;
NSLayoutAttributeBaseline: // 视图的基准线
NSLayoutAttributeLastBaseline: // 相当于NSLayoutAttributeBaseline;
NSLayoutAttributeFirstBaseline: // 文本上标线;
NSLayoutAttributeNotAnAttribute:// None;
NSLayoutAttributeLeading: // 在习惯由左向右看的地区,相当于NSLayoutAttributeLeft;在习惯从右至左看的地区,相当于NSLayoutAttributeRight;
NSLayoutAttributeTrailing:// 在习惯由左向右看的地区,相当于NSLayoutAttributeRight;在习惯从右至左看的地区,相当于NSLayoutAttributeLeft;
// 以下iOS8新增属性,各种间距,网络查阅不少文章也没具体介绍,暂时我也不清楚,有时间会再详细测试。
NSLayoutAttributeLeftMargin
NSLayoutAttributeRightMargin
NSLayoutAttributeTopMargin
NSLayoutAttributeBottomMargin
NSLayoutAttributeLeadingMargin
NSLayoutAttributeTrailingMargin
NSLayoutAttributeCenterXWithinMargins
NSLayoutAttributeCenterYWithinMargins

下面通过代码来了解constraintWithItem这个方法:

注: 使用Auto Layout时,必须先将视图的translatesAutoresizingMaskIntoConstraints属性设为NO。因为当它为YES时,自己添加的约束会与系统的Autoresizing Mask约束产生冲突。

// 创建两个视图view1和view2
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor brownColor];
[self.view addSubview:view1];
// 不需要给view设置frame
UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor orangeColor];
[self.view addSubview:view2];
// 关掉AutoresizingMask布局
view1.translatesAutoresizingMaskIntoConstraints = NO;
view2.translatesAutoresizingMaskIntoConstraints = NO;
// view1:设置宽度为100px
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:100]];
// 如果是设置view自身的属性,不涉及到与其他view的位置约束关系时。constraintWithItem:方法的toItem参数应设为nil;且第五个参数attribute应设为NSLayoutAttributeNotAnAttribute // 约束一定要添加在父视图上
// view1:设置高度等于宽度
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:view1
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0]];
// view1:设置在父视图水平方向上居中
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
// view1:设置在垂直方向上始终距离父视图底部为50px
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1
constant:-50]]; // 类似的Bottom、Right设置为负
// view2:设置宽度是view1宽度的0.5倍
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:view1
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0]];
// view2:设置垂直方向上距父视图顶部100px
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:100]];
// view2:设置垂直方向上距view1顶部50px
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:view1
attribute:NSLayoutAttributeTop
multiplier:1
constant:-50]];
// 通过以上两个条件可以确定高度,所以不需要再设置高度。
//view2:最后设置水平方向上距离父视图的左边间距与距离view1的间距相等
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:view1
attribute:NSLayoutAttributeLeft
multiplier:0.5
constant:0]];

运行程序查看效果,也可以大致参考下图的简单说明:

image


使用VFL(即Visual Format Language)可视化格式语言创建约束

VFL的API

+ (NSArray<NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format
options:(NSLayoutFormatOptions)opts
metrics:(nullable NSDictionary<NSString *,id> *)metrics
views:(NSDictionary<NSString *, id> *)views;

  • format:VFL格式字符串
  • opts: 对齐方式的枚举值NSLayoutFormatOptions
  • metrics:设置需要进行替换的值,是字典类型,通过下面代码加以理解
  • views:传入约束中用到的views,也是字典类型。注意字典中的values一定要和format参数里所写的views名字相同

部分NSLayoutFormatOptions的枚举值介绍:

NSLayoutFormatAlignAllLeft = (1 << NSLayoutAttributeLeft): // 左边对齐
NSLayoutFormatAlignAllRight = (1 << NSLayoutAttributeRight): // 右边对齐
NSLayoutFormatAlignAllTop = (1 << NSLayoutAttributeTop): // 顶部对齐
NSLayoutFormatAlignAllBottom = (1 << NSLayoutAttributeBottom): // 底部对齐
NSLayoutFormatAlignAllCenterX = (1 << NSLayoutAttributeCenterX):// 垂直方向中心对齐
NSLayoutFormatAlignAllCenterY = (1 << NSLayoutAttributeCenterY):// 水平方向中心对齐

下面通过代码来了解constraintsWithVisualFormat这个方法:

// 创建视图
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor brownColor];
[self.view addSubview:view1];
// 关掉AutoresizingMask
view1.translatesAutoresizingMaskIntoConstraints = NO;
// ①
NSDictionary *viewsDict = NSDictionaryOfVariableBindings(view1);
// ②
NSArray *constraints1 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[view1]-50-|"
options:0
metrics:nil
views:viewsDict];
// ③
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view1(==200)]"
options:0
metrics:nil
views:viewsDict];
// 使用addConstraints添加约束
[self.view addConstraints:constraints1];
[self.view addConstraints:constraints2];

下面介绍上面代码中的标记:
NSDictionaryOfVariableBindings(view1)
这是个宏定义,字典格式可以理解为 @{ @"view1" : @"view1"}; 相当于将key - value进行对应绑定;因为在VFL中是通过key来寻找对应的value。比如@{@"aView" : @"self.sView"}它会布局self.sView而不是aView。所以你在VFL中看到的变量是key,而不是value。

注:在format字符串里或是在binding字典里,都应该避免使用类似self.view1这样的字样,可以使用_view1

@"H:|-[view1]-50-|"
H: 表示在水平方向上添加约束(H即horizontal);
| 是表示父视图;
- 表示一个间距;如果未设置间距值,当和父视图之间时,默认代表20px(可看作-20-),如果是两个同级别的view,比如@"[view1]-[view2]",则表示8px;
所以这句话可理解为view1在水平方向上距父视图左边20px,距离父视图右边50px

@"V:|-100-[view1(==200)]"
V: 表示在垂直方向上添加约束(V即vertical);
[view1(==200)] 如果是在垂直方向上则表示view1的高度等200,水平方向则表示宽等某个值,可省略== 简化为[view1(200)]
由于确定了高度,所以底部间距不用设置,不然会起冲突。所以这句话可理解为view1在垂直方向上距父视图顶部100px,高为200px

再通过一个例子加深理解:

UIView *sView = [[UIView alloc] init];
sView.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:sView];
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor brownColor];
[sView addSubview:view1];
UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor orangeColor];
[sView addSubview:view2]; // 将view1 view2添加到sView上
// 关掉AutoresizingMask布局
sView.translatesAutoresizingMaskIntoConstraints = NO;
view1.translatesAutoresizingMaskIntoConstraints = NO;
view2.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDict = NSDictionaryOfVariableBindings(sView, view1, view2);
// 设置间距和宽度要进行替换的值
NSDictionary *metricsDict = @{@"padding" : @50, @"height" : @150};
// sView:水平方向布局
NSArray *constraints1 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-padding-[sView]-padding-|"
options:kNilOptions
metrics:metricsDict
views:viewsDict];
// sView:垂直方向布局
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[sView(height)]-padding-|"
options:kNilOptions
metrics:metricsDict
views:viewsDict];
// 使用addConstraints添加约束
[self.view addConstraints:constraints1];
[self.view addConstraints:constraints2];
// view1 - view2:水平方向布局
// 设置对齐方式,view2顶部与底部都与view1对齐
NSLayoutFormatOptions options = NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom;
NSArray *constraints3 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-15-[view1(100)]-padding-[view2]-15-|"
options:options
metrics:metricsDict
views:viewsDict];
// view1:垂直方向布局,根据设置的对齐方式可以确定view2高度与view1相等,所以不需要再进行对view2垂直方向上的布局
NSArray *constraints4 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-15-[view1]-15-|"
options:kNilOptions
metrics:metricsDict
views:viewsDict];
[sView addConstraints:constraints3];
[sView addConstraints:constraints4];
// 继续创建一个视图redView
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
// 设置redView的左右两端对齐sView且高度等于sView的高
NSArray *constraints5 = [NSLayoutConstraint
constraintsWithVisualFormat:@"V:[redView(sView)]-15-[sView]"
options:NSLayoutFormatAlignAllLeft | NSLayoutFormatAlignAllRight
metrics:nil
views:NSDictionaryOfVariableBindings(sView, redView)];
[self.view addConstraints:constraints5];

查看运行效果,大致如下:

image


相关问题

  • 假如设置三个视图的宽高固定为50px,如何让三个视图在垂直方向上居中且等分所有间距?
// 简单将宽高的约束进行封装
- (NSArray<NSLayoutConstraint *> *)constraintSize:(CGSize)size forView:(UIView *)view {
NSLayoutConstraint *constraintWidth = [NSLayoutConstraint
constraintWithItem:view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:size.width];
NSLayoutConstraint *constraintHeight = [NSLayoutConstraint
constraintWithItem:view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:size.height];
return @[constraintWidth, constraintHeight];
}
// 简单的将constraintWithItem方法简化封装
- (NSLayoutConstraint *)constraintView:(UIView *)item
attribute:(NSLayoutAttribute)attr1
toView:(UIView *)toView
attribute:(NSLayoutAttribute)attr2 {
return [NSLayoutConstraint constraintWithItem:item
attribute:attr1
relatedBy:NSLayoutRelationEqual
toItem:toView
attribute:attr2
multiplier:1
constant:0];
}

思路:因为是三个视图,所以会等分四个间距,可以通过辅助视图来填充间距,且每个辅助视图的宽是相等的,然后和固定的三个视图的宽的总和等于父视图的宽。详细参考下面代码:

// 设置三个视图的宽高固定为50px,让三个视图在垂直方向上居中且等分所有间距。
- (void)example7 {
NSInteger N = 3;
CGSize _size = CGSizeMake(50, 50);
// 创建第一个辅助视图,也是用它来保存上一个辅助视图
UIView *lastHelperView = [UIView new];
lastHelperView.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:lastHelperView];
lastHelperView.translatesAutoresizingMaskIntoConstraints = NO;
// 添加上边约束
[self.view addConstraint:[self constraintView:lastHelperView
attribute:NSLayoutAttributeTop
toView:self.view
attribute:NSLayoutAttributeTop]];
// 下
[self.view addConstraint:[self constraintView:lastHelperView
attribute:NSLayoutAttributeBottom
toView:self.view
attribute:NSLayoutAttributeBottom]];
// 左
[self.view addConstraint:[self constraintView:lastHelperView
attribute:NSLayoutAttributeLeft
toView:self.view
attribute:NSLayoutAttributeLeft]];
// 循环创建
for (NSInteger i = 0; i < N; i++) {
UIView *view = [UIView new];
view.backgroundColor = [UIColor orangeColor];
view.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:view];
// 固定view宽高为50px
[self.view addConstraints:[self constraintSize:_size forView:view]];
// CenterY居中
[self.view addConstraint:[self constraintView:view
attribute:NSLayoutAttributeCenterY
toView:self.view
attribute:NSLayoutAttributeCenterY]];
// 左边约束
[self.view addConstraint:[self constraintView:view
attribute:NSLayoutAttributeLeft
toView:lastHelperView
attribute:NSLayoutAttributeRight]];
UIView *helperView = [UIView new];
helperView.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:helperView];
helperView.translatesAutoresizingMaskIntoConstraints = NO;
// 上
[self.view addConstraint:[self constraintView:helperView
attribute:NSLayoutAttributeTop
toView:self.view
attribute:NSLayoutAttributeTop]];
// 下
[self.view addConstraint:[self constraintView:helperView
attribute:NSLayoutAttributeBottom
toView:self.view
attribute:NSLayoutAttributeBottom]];
// 等间距约束(即等宽)
[self.view addConstraint:[self constraintView:helperView
attribute:NSLayoutAttributeWidth
toView:lastHelperView
attribute:NSLayoutAttributeWidth]];
// 左
[self.view addConstraint:[self constraintView:helperView
attribute:NSLayoutAttributeLeft
toView:view
attribute:NSLayoutAttributeRight]];
lastHelperView = helperView;
}
// 为最后一个helperView添加右边约束
[self.view addConstraint:[self constraintView:lastHelperView
attribute:NSLayoutAttributeRight
toView:self.view
attribute:NSLayoutAttributeRight]];
}

运行查看效果:
image

以上就是对NSLayoutConstraint学习做的笔记和遇到的问题,后期会在进行深入理解研究~~
本文Demo已经上传至github,在里面的AutoLayout-Notes目录下。


参考资料

热评文章